// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2016, Google, Inc. All rights reserved
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#include <assert.h>
#include <debug.h>
#include <err.h>
#include <inttypes.h>
#include <platform.h>
#include <string.h>
#include <trace.h>
#include <zircon/compiler.h>

#include <dev/interrupt.h>
#include <dev/pci_config.h>
#include <dev/pcie_bridge.h>
#include <fbl/alloc_checker.h>
#include <lk/init.h>
#include <vm/vm.h>

using fbl::AutoLock;

#define LOCAL_TRACE 0

PcieBridge::PcieBridge(PcieBusDriver& bus_drv, uint bus_id, uint dev_id, uint func_id, uint mbus_id)
    : PcieDevice(bus_drv, bus_id, dev_id, func_id, true),
      PcieUpstreamNode(bus_drv, PcieUpstreamNode::Type::BRIDGE, mbus_id) {
  /* Assign the driver-wide region pool to this bridge's allocators. */
  DEBUG_ASSERT(driver().region_bookkeeping() != nullptr);
  pf_mmio_regions_.SetRegionPool(driver().region_bookkeeping());
  mmio_lo_regions_.SetRegionPool(driver().region_bookkeeping());
  mmio_hi_regions_.SetRegionPool(driver().region_bookkeeping());
  pio_regions_.SetRegionPool(driver().region_bookkeeping());
}

fbl::RefPtr<PcieDevice> PcieBridge::Create(PcieUpstreamNode& upstream, uint dev_id, uint func_id,
                                           uint managed_bus_id) {
  fbl::AllocChecker ac;
  auto raw_bridge = new (&ac)
      PcieBridge(upstream.driver(), upstream.managed_bus_id(), dev_id, func_id, managed_bus_id);
  if (!ac.check()) {
    DEBUG_ASSERT(raw_bridge == nullptr);
    TRACEF("Out of memory attemping to create PCIe bridge %02x:%02x.%01x.\n",
           upstream.managed_bus_id(), dev_id, func_id);
    return nullptr;
  }

  auto bridge = fbl::AdoptRef(static_cast<PcieDevice*>(raw_bridge));
  zx_status_t res = raw_bridge->Init(upstream);
  if (res != ZX_OK) {
    TRACEF("Failed to initialize PCIe bridge %02x:%02x.%01x. (res %d)\n", upstream.managed_bus_id(),
           dev_id, func_id, res);
    return nullptr;
  }

  return bridge;
}

zx_status_t PcieBridge::Init(PcieUpstreamNode& upstream) {
  AutoLock dev_lock(&dev_lock_);

  // Initialize the device portion of ourselves first.
  zx_status_t res = PcieDevice::InitLocked(upstream);
  if (res != ZX_OK)
    return res;

  // Sanity checks of bus allocation.
  //
  // TODO(johngro) : Strengthen sanity checks around bridge topology and
  // handle the need to reconfigure bridge topology if a bridge happens to be
  // misconfigured.  Right now, we just assume that the BIOS/Bootloader has
  // taken care of bridge configuration.  In the short term, it would be good
  // to add some protection against cycles in the bridge configuration which
  // could lead to infinite recursion.
  uint primary_id = cfg_->Read(PciConfig::kPrimaryBusId);
  uint secondary_id = cfg_->Read(PciConfig::kSecondaryBusId);

  if (primary_id == secondary_id) {
    TRACEF(
        "PCI-to-PCI bridge detected at %02x:%02x.%01x claims to be bridged to itsef "
        "(primary %02x == secondary %02x)... skipping scan.\n",
        bus_id_, dev_id_, func_id_, primary_id, secondary_id);
    return ZX_ERR_BAD_STATE;
  }

  if (primary_id != bus_id_) {
    TRACEF(
        "PCI-to-PCI bridge detected at %02x:%02x.%01x has invalid primary bus id "
        "(%02x)... skipping scan.\n",
        bus_id_, dev_id_, func_id_, primary_id);
    return ZX_ERR_BAD_STATE;
  }

  if (secondary_id != managed_bus_id()) {
    TRACEF(
        "PCI-to-PCI bridge detected at %02x:%02x.%01x has invalid secondary bus id "
        "(%02x)... skipping scan.\n",
        bus_id_, dev_id_, func_id_, secondary_id);
    return ZX_ERR_BAD_STATE;
  }

  // Parse the state of its I/O and Memory windows.
  res = ParseBusWindowsLocked();
  if (res != ZX_OK)
    return res;

  // Things went well, flag the device as plugged in and link ourselves up to
  // the graph.
  plugged_in_ = true;
  driver().LinkDeviceToUpstream(*this, upstream);

  // Release the device lock, then recurse and scan for downstream devices.
  dev_lock.release();
  ScanDownstream();
  return res;
}

zx_status_t PcieBridge::ParseBusWindowsLocked() {
  DEBUG_ASSERT(dev_lock_.IsHeld());

  // Parse the currently configured windows used to determine MMIO/PIO
  // forwarding policy for this bridge.
  //
  // See The PCI-to-PCI Bridge Architecture Specification Revision 1.2,
  // section 3.2.5 and chapter 4 for detail.
  uint32_t base, limit;

  // I/O window
  base = cfg_->Read(PciConfig::kIoBase);
  limit = cfg_->Read(PciConfig::kIoLimit);

  supports_32bit_pio_ = ((base & 0xF) == 0x1) && ((base & 0xF) == (limit & 0xF));
  io_base_ = (base & ~0xF) << 8;
  io_limit_ = (limit << 8) | 0xFFF;
  if (supports_32bit_pio_) {
    io_base_ |= static_cast<uint32_t>(cfg_->Read(PciConfig::kIoBaseUpper)) << 16;
    io_limit_ |= static_cast<uint32_t>(cfg_->Read(PciConfig::kIoLimitUpper)) << 16;
  }

  // Non-prefetchable memory window
  mem_base_ = (static_cast<uint32_t>(cfg_->Read(PciConfig::kMemoryBase)) << 16) & ~0xFFFFF;
  mem_limit_ = (static_cast<uint32_t>(cfg_->Read(PciConfig::kMemoryLimit)) << 16) | 0xFFFFF;

  // Prefetchable memory window
  base = cfg_->Read(PciConfig::kPrefetchableMemoryBase);
  limit = cfg_->Read(PciConfig::kPrefetchableMemoryLimit);

  bool supports_64bit_pf_mem = ((base & 0xF) == 0x1) && ((base & 0xF) == (limit & 0xF));
  pf_mem_base_ = (base & ~0xF) << 16;
  pf_mem_limit_ = (limit << 16) | 0xFFFFF;
  if (supports_64bit_pf_mem) {
    pf_mem_base_ |= static_cast<uint64_t>(cfg_->Read(PciConfig::kPrefetchableMemoryBaseUpper))
                    << 32;
    pf_mem_limit_ |= static_cast<uint64_t>(cfg_->Read(PciConfig::kPrefetchableMemoryLimitUpper))
                     << 32;
  }

  return ZX_OK;
}

void PcieBridge::Dump() const {
  PcieDevice::Dump();

  printf("\tbridge managed bus id %#02x\n", managed_bus_id());
  printf("\tio base %#x limit %#x\n", io_base(), io_limit());
  printf("\tmem base %#x limit %#x\n", mem_base(), mem_limit());
  printf("\tprefectable base %#" PRIx64 " limit %#" PRIx64 "\n", pf_mem_base(), pf_mem_limit());
}

void PcieBridge::Unplug() {
  PcieDevice::Unplug();
  PcieUpstreamNode::UnplugDownstream();
}

zx_status_t PcieBridge::AllocateBars() {
  AutoLock dev_lock(&dev_lock_);

  // Start by making sure we can allocate our bridge windows.
  zx_status_t res = AllocateBridgeWindowsLocked();
  if (res != ZX_OK)
    return res;

  // Now, attempt to allocate our device BARs.
  res = PcieDevice::AllocateBarsLocked();
  if (res != ZX_OK)
    return res;

  // Great, we are good to go.  Leave our device lock and attempt to allocate
  // our downstream devices' resources.
  dev_lock.release();
  PcieUpstreamNode::AllocateDownstreamBars();
  return ZX_OK;
}

zx_status_t PcieBridge::AllocateBridgeWindowsLocked() {
  zx_status_t ret;
  DEBUG_ASSERT(dev_lock_.IsHeld());

  // Hold a reference to our upstream node while we do this.  If we cannot
  // obtain a reference, then our upstream node has become unplugged and we
  // should just fail out now.
  auto upstream = GetUpstream();
  if (upstream == nullptr)
    return ZX_ERR_UNAVAILABLE;

  // We are configuring a bridge.  We need to be able to allocate the MMIO and
  // PIO regions this bridge is configured to manage.  Currently, we don't
  // support re-allocating a bridge's MMIO/PIO windows.
  //
  // TODO(johngro) : support dynamic configuration of bridge windows.  Its
  // going to be important when we need to support hot-plugging.  See ZX-322
  //
  if (io_base_ <= io_limit_) {
    uint64_t size = static_cast<uint64_t>(io_limit_) - io_base_ + 1;
    ret = upstream->pio_regions().GetRegion({.base = io_base_, .size = size}, pio_window_);

    if (ret != ZX_OK) {
      TRACEF("Failed to allocate bridge PIO window [0x%08x, 0x%08x]\n", io_base_, io_limit_);
      return ret;
    }

    DEBUG_ASSERT(pio_window_ != nullptr);
    pio_regions().AddRegion(*pio_window_);
  }

  if (mem_base_ <= mem_limit_) {
    uint64_t size = mem_limit_ - mem_base_ + 1;
    ret = upstream->mmio_lo_regions().GetRegion({.base = mem_base_, .size = size}, mmio_window_);

    if (ret != ZX_OK) {
      TRACEF("Failed to allocate bridge MMIO window [0x%08x, 0x%08x]\n", mem_base_, mem_limit_);
      return ret;
    }

    DEBUG_ASSERT(mmio_window_ != nullptr);
    mmio_lo_regions().AddRegion(*mmio_window_);
  }

  if (pf_mem_base_ <= pf_mem_limit_) {
    uint64_t size = pf_mem_limit_ - pf_mem_base_ + 1;

    // Attempt to allocate out of the upstream's prefetchable region.
    ret = upstream->pf_mmio_regions().GetRegion({.base = pf_mem_base_, .size = size},
                                                pf_mmio_window_);
    if (ret != ZX_OK) {
      // We failed. If it's the root bridge try to allocate from its MMIO regions.
      if (upstream->type() == PcieUpstreamNode::Type::ROOT) {
        ret = upstream->mmio_lo_regions().GetRegion({.base = pf_mem_base_, .size = size},
                                                    pf_mmio_window_);
        if (ret != ZX_OK) {
          ret = upstream->mmio_hi_regions().GetRegion({.base = pf_mem_base_, .size = size},
                                                      pf_mmio_window_);
        }
      }
    }

    if (ret != ZX_OK) {
      TRACEF(
          "Failed to allocate bridge prefetcable MMIO window "
          "[%#" PRIx64 ", %#" PRIx64 "]\n",
          pf_mem_base_, pf_mem_limit_);
      return ret;
    }

    DEBUG_ASSERT(pf_mmio_window_ != nullptr);
    pf_mmio_regions().AddRegion(*pf_mmio_window_);
  }

  return ZX_OK;
}

void PcieBridge::Disable() {
  DEBUG_ASSERT(!dev_lock_.IsHeld());

  // Immediately enter the device lock and enter the disabled state.  We want
  // to be outside of the device lock as we disable our downstream devices,
  // but we don't want any new devices to be able to plug into us as we do so.
  {
    AutoLock dev_lock(&dev_lock_);
    disabled_ = true;
  }

  // Start by disabling all of our downstream devices.  This should prevent
  // the from bothering us moving forward.  Do not hold the device lock while
  // we do this.
  PcieUpstreamNode::DisableDownstream();

  // Enter the device lock again and finish shooting ourselves in the head.
  {
    AutoLock dev_lock(&dev_lock_);

    // Disable the device portion of ourselves.
    PcieDevice::DisableLocked();

    // Close all of our IO windows at the HW level and update the internal
    // bookkeeping to indicate that they are closed.
    cfg_->Write(PciConfig::kIoBase, 0xF0);
    cfg_->Write(PciConfig::kIoLimit, 0);
    cfg_->Write(PciConfig::kIoBaseUpper, 0);
    cfg_->Write(PciConfig::kIoLimitUpper, 0);

    cfg_->Write(PciConfig::kMemoryBase, 0xFFF0);
    cfg_->Write(PciConfig::kMemoryLimit, 0);

    cfg_->Write(PciConfig::kPrefetchableMemoryBase, 0xFFF0);
    cfg_->Write(PciConfig::kPrefetchableMemoryLimit, 0);
    cfg_->Write(PciConfig::kPrefetchableMemoryBaseUpper, 0);
    cfg_->Write(PciConfig::kPrefetchableMemoryLimitUpper, 0);

    pf_mem_limit_ = mem_limit_ = io_limit_ = 0u;
    pf_mem_base_ = mem_base_ = io_base_ = 1u;

    // Release our internal bookkeeping
    mmio_lo_regions().Reset();
    mmio_hi_regions().Reset();
    pio_regions().Reset();

    mmio_window_.reset();
    pio_window_.reset();
  }
}
